//
//  YJLabel.swift
//  YJLabelDemo
//
//  Created by 杨永杰 on 2019/8/20.
//  Copyright © 2019年 YYongJie. All rights reserved.
//

import UIKit

@objc
public protocol YJLabelDelegate: NSObjectProtocol {
   @objc optional func yjLabelDidSelectedLinkText(label: YJLabel, text: String)
}

/**
 1. 使用 TextKit 接管 Label 的底层实现 - ’绘制‘ textStorage 的文本内容
 2. 使用正则表达式过滤 URL
 3. 交互
 
 - UILabel 默认不能实现垂直顶部对齐，使用TextKit可以
 - 提示：
    - 在 iOS 7.0 之前，要实现类似的效果，需要使用 CoreText，使用起来异常繁琐
    - YYText 框架，是作者自己建立了一套渲染系统
 */
public class YJLabel: UILabel {
    
    // MARK: - public properties (公有属性)
    public var lineTextColor = UIColor.blue
    public var selectedTextBackgroundColor = UIColor.lightGray
    public weak var delegate: YJLabelDelegate?
    
    
    // MARK: - private properties (私有属性)
    private let patterns = ["[a-zA-Z]*://[a-zA-Z0-9/\\.]*", "#.*?#", "@[\\u4e00-\\u9fa5a-zA-Z0-9_-]*"]
    private lazy var linkRanges = [NSRange]()
    /// 选中的 range
    private var selectedRange: NSRange?
    /// 负责文本存储
    private lazy var textStorage = NSTextStorage()
    /// 负责文本 ’字形‘ 布局
    private lazy var layoutManager = NSLayoutManager()
    /// 设定文字绘制的范围
    private lazy var textContainer = NSTextContainer()
    
    
    // MARK: - init functions (构造函数)
    override init(frame: CGRect) {
        super.init(frame: frame)
        prepareLabel()
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        prepareLabel()        
    }
    
    /// 绘制文本
    override public func drawText(in rect: CGRect) {
        let range = NSRange(location: 0, length: textStorage.length)
        // 这里会根据 textStorage.addAttributes 添加的属性绘制
        layoutManager.drawBackground(forGlyphRange: range, at: CGPoint())
        layoutManager.drawGlyphs(forGlyphRange: range, at: CGPoint())
    }
    
    override public func layoutSubviews() {
        super.layoutSubviews()
        // 指定绘制文本的区域
        textContainer.size = bounds.size
    }

    
    // MARK: - touch events (触摸事件)
    override public func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        
        // 获取点击的位置
        guard let location = touches.first?.location(in: self) else {
            return
        }
        // 获取 location 所在的 range
        selectedRange = linkRangeAtLocation(location: location)
        // 修改 selectedRange 对应的属性
        modifySelectedAttribute(isSelected: true)
    }
    
    var i = 0
    public override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        // 获取点击的位置
        guard let location = touches.first?.location(in: self) else {
            return
        }
        
        // location 不在 link 上时，置为未选中，return
        guard let range = linkRangeAtLocation(location: location) else {
            modifySelectedAttribute(isSelected: false)
            return
        }
        
        // location 在 link 上，且不在 selectedRange 对应的 link
        if !(range.location == selectedRange?.location && range.length == selectedRange?.length) {
            
            // 将 selectedRange，置为未选中
            modifySelectedAttribute(isSelected: false)
            // 更新 selectedRange
            selectedRange = range
            // 将 selectedRange 置为选中
            modifySelectedAttribute(isSelected: true)
        }
    }
    
    override public func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {

        guard let selectedRange = selectedRange else { return }

        // 获取选中的 text
        let selectedText = textStorage.attributedSubstring(from: selectedRange).string
        // 调用代理方法
        delegate?.yjLabelDidSelectedLinkText?(label: self, text: selectedText)
        
        // 修改 selectedRange 对应的属性
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.25) {
            self.modifySelectedAttribute(isSelected: false)
        }
    }
    
    public override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
        modifySelectedAttribute(isSelected: false)
    }
    
    // MARK: - override properties
    override public var text: String? {
        didSet {
            updateTextStorage()
        }
    }
    
    override public var attributedText: NSAttributedString? {
        didSet {
            updateTextStorage()
        }
    }
    
    override public var font: UIFont! {
        didSet {
            updateTextStorage()
        }
    }
    
    override public var textColor: UIColor! {
        didSet {
            updateTextStorage()
        }
    }
}


// MARK: - 正则表达式函数
private extension YJLabel {
    
    // use regex check all link ranges (使用正则表达式检查所有链接范围)
    func regexLinkRanges(attrString: NSAttributedString) {
        
        // 移除之前的 range
        linkRanges.removeAll()
        
        // 确定正则匹配范围
        let regexRange = NSRange(location: 0, length: attrString.length)
        
        // 遍历正则条件，依次匹配
        for pattern in patterns {
            
            let regex = try! NSRegularExpression(pattern: pattern, options: [])
            let results = regex.matches(in: attrString.string, options: [], range: regexRange)
            
            for r in results {
                linkRanges.append(r.range(at: 0))
            }
        }
    }
}

// MARK: - 设置 TextKit 核心对象
private extension YJLabel {
    
    /// 准备label
    func prepareLabel() {
        
        // 设置对象关系
        textStorage.addLayoutManager(layoutManager)
        layoutManager.addTextContainer(textContainer)
        
        textContainer.lineFragmentPadding = 0
        
        // 开启交互
        isUserInteractionEnabled = true
    }
    
    
    /// update text storage and redraw text (更新 storage，重新绘制)
    func updateTextStorage() {
        
        guard let attributedText = attributedText else {
            return
        }
        
        let attrStringM = addLineBreak(attrString: attributedText)
        regexLinkRanges(attrString: attrStringM)
        addLinkAttribute(attrStringM: attrStringM)
        
        textStorage.setAttributedString(attrStringM)
        setNeedsDisplay()
    }
}

// MARK: - other private methods
private extension YJLabel {
    
    // add link attributes (添加链接属性)
    func addLinkAttribute(attrStringM: NSMutableAttributedString) {
        
        if attrStringM.length <= 0 {
            return
        }
        
        var range = NSRange(location: 0, length: 0)
        var attributes = attrStringM.attributes(at: 0, effectiveRange: &range)
        
        attributes[NSAttributedString.Key.font] = font!
        attributes[NSAttributedString.Key.foregroundColor] = textColor
        attrStringM.addAttributes(attributes, range: range)

        attributes[NSAttributedString.Key.foregroundColor] = lineTextColor
        
        for r in linkRanges {
            attrStringM.setAttributes(attributes, range: r)
        }
    }
    
    // add line break mode (添加断行模式)
    func addLineBreak(attrString: NSAttributedString) -> NSMutableAttributedString {
        
        let attrStringM = NSMutableAttributedString(attributedString: attrString)
        
        if attrStringM.length <= 0 {
            return attrStringM
        }
        
        /// 返回 指定索引的字符 的属性集合
        ///
        /// - Parameters:
        ///   - at location: 指定索引
        ///   - range: 与 指定索的字符的属性完全相同 且 与指定索引相邻(属性没有发生过变化) 的范围
        /// - Returns: 属性集合
        /// attributes(at location: Int, effectiveRange range: NSRangePointer?) -> [NSAttributedString.Key : Any]
        var range = NSRange(location: 0, length: 0)
        var attributes = attrStringM.attributes(at: 1, effectiveRange: &range)

        var paragraphStyle = attributes[NSAttributedString.Key.paragraphStyle] as? NSMutableParagraphStyle
        
        if paragraphStyle != nil {
            paragraphStyle!.lineBreakMode = NSLineBreakMode.byWordWrapping
        } else {
            // iOS 8.0 can not get the paragraphStyle directly
            paragraphStyle = NSMutableParagraphStyle()
            paragraphStyle?.lineBreakMode = NSLineBreakMode.byWordWrapping
            attributes[NSAttributedString.Key.paragraphStyle] = paragraphStyle
            
            attrStringM.setAttributes(attributes, range: range)
        }
        
        return attrStringM
    }
    
    // find selectedRange (找出用户选中的 range)
    func linkRangeAtLocation(location: CGPoint) -> NSRange? {
        
        if textStorage.length <= 0 {
            return nil
        }

        // 获取当前选中字符的索引，
        let index = layoutManager.glyphIndex(for: location, in: textContainer)

        // 遍历 range，找出 index 所在的 range，
        for range in linkRanges {
            if NSLocationInRange(index, range) {
                // 获取 range 所在的 rect，找出 location 所在的 rect，返回该 rect 对应的 range
                let rect = layoutManager.boundingRect(forGlyphRange: range, in: textContainer)
                if rect.contains(location) {
                    return range
                }
            }
        }
        return nil
    }
    
    // 修改选中文本的属性
    func modifySelectedAttribute(isSelected: Bool) {
        
        guard let selectedRange = selectedRange else {
            return
        }
        
        var attributes = textStorage.attributes(at: 0, effectiveRange: nil)
        attributes[NSAttributedString.Key.foregroundColor] = lineTextColor
        attributes[NSAttributedString.Key.backgroundColor] = isSelected ? selectedTextBackgroundColor : UIColor.clear
        isSelected ? () : (self.selectedRange = nil)
        
        textStorage.addAttributes(attributes, range: selectedRange)
        setNeedsDisplay()
    }
}
